using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Data.Schema.Extensibility;
using Microsoft.Data.Schema.SchemaModel;
using Microsoft.Data.Schema.ScriptDom.Sql;
using Microsoft.Data.Schema.Sql.SchemaModel;
using Microsoft.Data.Schema.Sql;
using Microsoft.Data.Schema.StaticCodeAnalysis;

using System.Collections;
using System.Linq;
using System.IO;

namespace Neznayka
{
    /// <summary>
    /// This is a SQL rule which returns a warning message 
    /// whenever there is a variable used that has never been assigned to.
    /// </summary>

    [DatabaseSchemaProviderCompatibility(typeof(SqlDatabaseSchemaProvider))]
    [DataRuleAttribute(
        NeznaykaConstants.NameSpace,
        NeznaykaConstants.AvoidUninitialisedVariablesRuleId,
        NeznaykaConstants.ResourceBaseName,
        NeznaykaConstants.AvoidUninitialisedVariables_RuleName,
        NeznaykaConstants.CategoryVariableUsage,
        DescriptionResourceId = NeznaykaConstants.AvoidUninitialisedVariables_ProblemDescription)]
    [SupportedElementType(typeof(ISqlFunction))]
    [SupportedElementType(typeof(ISqlTableValuedFunction))]
    [SupportedElementType(typeof(ISqlMultiStatementTableValuedFunction))]
    [SupportedElementType(typeof(ISqlProcedure))]
    [SupportedElementType(typeof(ISqlTrigger))]
    public class AvoidUninitialisedVariablesRule : StaticCodeAnalysisRule
    {
        #region Overrides
        /// <summary>
        /// Analyze the model element
        /// </summary>
        public override IList<DataRuleProblem> Analyze(DataRuleSetting ruleSetting, DataRuleExecutionContext context)
        {

            // (Re)-Load Environment settings
            List<DataRuleProblem> problems;
            SqlSchemaModel sqlSchemaModel;
            ISqlModelElement sqlElement;
            TSqlFragment sqlFragment;
            DMVRuleSetup.RuleSetup(context, out problems, out sqlSchemaModel, out sqlElement, out sqlFragment);

            // visitor to get the declarations of variables - that have no initialiser
            UninitialisedVariableDeclarationVisitor declarationVisitor = new UninitialisedVariableDeclarationVisitor();
            sqlFragment.Accept(declarationVisitor);
            List<Literal> variableDeclarations = declarationVisitor.VariableDeclarations;

            // visitor to get parameter names - these look like variables and need removing
            // from variable references before we use them
            NamedParameterUsageVisitor namedParameterUsageVisitor = new NamedParameterUsageVisitor();
            sqlFragment.Accept(namedParameterUsageVisitor);
            IEnumerable<Literal> namedParameters = namedParameterUsageVisitor.NamedParameters;
            // visitor to get the occurrences of variables
            VariableUsageVisitor usageVisitor = new VariableUsageVisitor();
            sqlFragment.Accept(usageVisitor);
            List<Literal> allVariableLikeReferences = usageVisitor.VariableReferences;
            // remove all named parameters from the list of referenced variables
            IEnumerable<Literal> tmpVR = allVariableLikeReferences.Except(namedParameters, new SqlLiteralComparer());
            List<Literal> variableReferences = tmpVR.ToList();

            // get all assignments to variables
            VariableAssignmentVisitor usageWriteVisitor = new VariableAssignmentVisitor();
            sqlFragment.Accept(usageWriteVisitor);
            List<Literal> variableWriteOccurrences = usageWriteVisitor.VariableAssignments;

            Dictionary<string, object> objects = new Dictionary<string, object>(SqlComparer.Comparer);
            Dictionary<string, int> counts = new Dictionary<string, int>(SqlComparer.Comparer);
            Dictionary<string, int> writeCounts = new Dictionary<string, int>(SqlComparer.Comparer);


            foreach (Literal variableDeclaration in variableDeclarations)
            {
                objects.Add(variableDeclaration.Value, variableDeclaration);
            }

            foreach (Literal variableReference in variableReferences)
            {
                if (!counts.ContainsKey(variableReference.Value))
                {
                    counts.Add(variableReference.Value, 1);
                }
                else {
                    counts[variableReference.Value]++;
                }
            }
            foreach (Literal variableWriteOccurrence in variableWriteOccurrences)
            {
                if (!writeCounts.ContainsKey(variableWriteOccurrence.Value))
                {
                    writeCounts.Add(variableWriteOccurrence.Value, 1);
                }
                else
                {
                    counts[variableWriteOccurrence.Value]++;
                }
            }
            foreach (var key in objects.Keys)
            {

                if ((counts.ContainsKey(key)) && ((counts[key]) >1)  && (!(writeCounts.ContainsKey(key)))) {
                    DataRuleProblem problem = new DataRuleProblem(this,
                                                String.Format(CultureInfo.CurrentCulture, this.RuleProperties.Description, SqlRuleUtils.GetElementName(sqlSchemaModel, sqlElement)),
                                                sqlElement);

                    SqlRuleUtils.UpdateProblemPosition(problem, ((Literal)objects[key]).StartOffset, ((Literal)objects[key]).FragmentLength);
                    problems.Add(problem);
                }
            }


            return problems;
        }

        #endregion    
 
    }
}